using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
//ok

namespace WCFDicing.DataContracts
{
    /// <summary>
    /// klasa opisujaca stol gry w kosci
    /// </summary>
    [System.Runtime.Serialization.DataContractAttribute( Namespace = "http://WCFDicing.DataContracts/2008/01", Name = "DicingTable" )]
    public class DicingTable
    {
        #region Fields + Get

        /// <summary>
        /// opcje stolu
        /// </summary>
        private DicingRoomOption options;

        /// <summary>
        /// get i set opcje stolu
        /// </summary>
        [System.Runtime.Serialization.DataMemberAttribute( IsRequired = false, Name = "Options", Order = 0 )]
        public DicingRoomOption Options
        {
            get { return options; }
            set { options = value; }
        }

        /// <summary>
        /// krzesla
        /// </summary>
        private Dictionary<int, DicingChair> chairs;

        /// <summary>
        /// get i set krzesla
        /// </summary>
        [System.Runtime.Serialization.DataMemberAttribute( IsRequired = false, Name = "Chairs", Order = 1 )]
        public Dictionary<int, DicingChair> Chairs
        {
            get { return chairs; }
            set { chairs = value; }
        }

        /// <summary>
        /// gracze
        /// </summary>
        private Dictionary<int, DicingPlayer> players;

        /// <summary>
        /// get i set gracze
        /// </summary>
        [System.Runtime.Serialization.DataMemberAttribute( IsRequired = false, Name = "Players", Order = 2 )]
        public Dictionary<int, DicingPlayer> Players
        {
            get { return players; }
            set { players = value; }
        }

        /// <summary>
        /// cel rzucajacego
        /// </summary>
        private int aim;

        /// <summary>
        /// get i set cel rzucajacego
        /// </summary>
        [System.Runtime.Serialization.DataMemberAttribute( IsRequired = false, Name = "Aim", Order = 3 )]
        public int Aim
        {
            get { return aim; }
            set { aim = value; }
        }

        /// <summary>
        /// gracz rzucajacy
        /// </summary>
        private int playerThrowId;

        /// <summary>
        /// get i set gracz rzucajacy
        /// </summary>
        [System.Runtime.Serialization.DataMemberAttribute( IsRequired = false, Name = "PlayerThrowId", Order = 4 )]
        public int PlayerThrowId
        {
            get { return playerThrowId; }
            set { playerThrowId = value; }
        }

        /// <summary>
        /// gracz aktualnie obstawiajacy
        /// </summary>
        private int playerBidId;

        /// <summary>
        /// get i set gracz aktualnie obstawiajacy
        /// </summary>
        [System.Runtime.Serialization.DataMemberAttribute( IsRequired = false, Name = "PlayerBidId", Order = 5 )]
        public int PlayerBidId
        {
            get { return playerBidId; }
            set { playerBidId = value; }
        }

        /// <summary>
        /// wlasciciel stolu
        /// </summary>
        private int playerOwnerId;

        /// <summary>
        /// get i set wlasciciel stolu
        /// </summary>
        [System.Runtime.Serialization.DataMemberAttribute( IsRequired = false, Name = "PlayerOwnerId", Order = 6 )]
        public int PlayerOwnerId
        {
            get { return playerOwnerId; }
            set { playerOwnerId = value; }
        }

        /// <summary>
        /// aktualna akcja na stole
        /// </summary>
        private DicingActionList tableAction;

        /// <summary>
        /// get i set aktualna akcja na stole
        /// </summary>
        [System.Runtime.Serialization.DataMemberAttribute( IsRequired = false, Name = "TableAction", Order = 7 )]
        public DicingActionList TableAction
        {
            get { return tableAction; }
            set { tableAction = value; }
        }

        /// <summary>
        /// maksymalny zaklad mozliwy w tym momencie
        /// </summary>
        private int currentMaxBet;

        /// <summary>
        /// get i set maksymalny zaklad mozliwy w tym momencie
        /// </summary>
        [System.Runtime.Serialization.DataMemberAttribute( IsRequired = false, Name = "CurrentMaxBet", Order = 8 )]
        public int CurrentMaxBet
        {
            get { return currentMaxBet; }
            set { currentMaxBet = value; }
        }

        /// <summary>
        /// wlasciwosci rzutu
        /// </summary>
        private DicingAnimation animation = new DicingAnimation();

        /// <summary>
        /// get i set wlasciwosci rzutu
        /// </summary>
        [System.Runtime.Serialization.DataMemberAttribute( IsRequired = false, Name = "Animation", Order = 9 )]
        public DicingAnimation Animation
        {
            get { return animation; }
            set { animation = value; }
        }

        /// <summary>
        /// wyrzucone kosci
        /// </summary>
        private DicingResult result = new DicingResult();

        /// <summary>
        /// get i set wyrzucone kosci
        /// </summary>
        /// 
        [System.Runtime.Serialization.DataMemberAttribute( IsRequired = false, Name = "Result", Order = 10 )]
        public DicingResult Result
        {
            get { return result; }
            set { result = value; }
        }



        #endregion

        #region konstruktory

        /// <summary>
        /// konstruktor domyslny
        /// </summary>
        public DicingTable() { }

        /// <summary>
        /// konstruktor z nadaniem stolowi opcji gry
        /// </summary>
        /// <param name="opt">opcje gry</param>
        public DicingTable(DicingRoomOption opt)
        {
            //kopiujemy opcje
            options = new DicingRoomOption(opt);

            //tworzymy krzesla
            chairs = new Dictionary<int,DicingChair>(opt.MaximumPlayers);
            for ( int i = 0; i < opt.MaximumPlayers; i++ )
            {
                chairs.Add( i, new DicingChair( i ) );
            }

            //ustawiamy pierwsze krzeslo jako wlasciciela
            chairs[0].makeThisOwner( );

            players = new Dictionary<int, DicingPlayer>( );
            tableAction = DicingActionList.FirstRound;
            playerOwnerId = -1;
        }

        /// <summary>
        /// konstruktor kopiujacy
        /// </summary>
        /// <param name="tab">obiekt z ktorego kopiujemy</param>
        public DicingTable( DicingTable tab )
        {
            this.options = new DicingRoomOption( tab.Options );
            this.aim = tab.Aim;
            this.chairs = new Dictionary<int, DicingChair>( tab.Chairs );
            this.currentMaxBet = tab.CurrentMaxBet;
            this.playerBidId = tab.PlayerBidId;
            this.playerOwnerId = tab.PlayerOwnerId;
            this.players = new Dictionary<int, DicingPlayer>( tab.Players );
            this.playerThrowId = tab.PlayerThrowId;
            this.tableAction = tab.TableAction;
            this.animation = new DicingAnimation( tab.Animation );
            this.result = new DicingResult( tab.Result );
        }

        #endregion

        /// <summary>
        /// dolacza gracza do gry
        /// </summary>
        /// <param name="player">gracz dolanczany</param>
        /// <returns>czy sie powiodlo</returns>
        public bool Join( DicingPlayer player )
        {
            bool result = false;
            //ustawiamy id krzesla i sadzamy gracza
            foreach ( DicingChair chair in chairs.Values )
            {
                if ( !chair.IsPlaying )
                {
                    chair.SitDown( player.PlayerId );
                    player.ChairId = chair.ChairId;
                    result = true;
                    break;
                }
            }
            if ( !result )      //gdy nie znalezlismy wolnego krzesla
                return false;
            
            //dodajemy do listy graczy
            players.Add( player.PlayerId, player );
            player.PrepareForNewRound( );

            //sprawdzamy czy stol ma wlasciciela
            //jesli nie ma to nowy gracz jest wlascicielem
            if ( playerOwnerId == -1 )
            {
                playerOwnerId = player.PlayerId;
                playerBidId = player.PlayerId;          //ustawiamy tez jako aktualnego gracza
            }

            return true;
        }

        /// <summary>
        /// obsluga odejscia gracza od gry
        /// </summary>
        /// <param name="idPlayer">id odchodzacego gracza</param>
        /// <returns>czy operacja sie udala</returns>
        public bool Leave( int idPlayer )
        {
            //spradzamy czy mamy takiego gracza
            DicingPlayer currentPlayer;
            if ( !players.TryGetValue( idPlayer, out currentPlayer ) )
                return false;

            //sprawdzamy czy jest wlascicielem
            if ( playerOwnerId == idPlayer )
                FindNewOwner( );    //jesli tak szukamy nowego wlasciciela

            //sprawdzamy czy nie jest to ucieczka gracza rozgrywajacego
            if ( idPlayer == this.playerThrowId &&
                ( tableAction == DicingActionList.Throwing || tableAction == DicingActionList.Aiming ) )
            {
                LostThrow( );       //jesli jest to gracz przegral
                playerBidId = chairs[PrevChair( players[idPlayer].ChairId )].PlayerId;      //ustawiamy na rozgrywajacego nowego playera
                playerThrowId = playerBidId;
            }
            else if ( idPlayer == this.playerBidId &&
                ( tableAction == DicingActionList.NewRound || tableAction == DicingActionList.FirstRound ) )
                //sprawdzamy czy byl to odpowiedni gosc do zaczecia gry
                playerBidId = playerOwnerId;        //jesli tak to teraz tym gosciem jest wlasciciel stolu
            else if ( idPlayer == this.playerBidId ) //sprawdzamy czy mial obstawiac, jesli 
                //tak to dajemy obstawic nastepnemu graczowi w kolejce
                NextBet( );

            //gracz wstaje z krzesla
            chairs[players[idPlayer].ChairId].StandUp( );

            //wymazujemy go z tablicy graczy
            players.Remove( idPlayer );

            return true;
        }

        /// <summary>
        /// znajduje nowego wlascieciela stolu
        /// </summary>
        private void FindNewOwner()
        {
            if ( players.Count <= 1 )   //gdy jest tylko jeden gracz to nie ma co szukac
                playerOwnerId = -1;
            else
            {
                //szukamy po kolei
                foreach ( DicingPlayer play in players.Values )
                {
                    if ( play.PlayerId != playerOwnerId )
                    {
                        chairs[play.ChairId].makeThisOwner( );      //ustawiamy krzeslo gracza jako wlasciciela
                        playerOwnerId = play.PlayerId;
                        break;      //konczymy poszukiwania
                    }
                }
            }
        }

        /// <summary>
        /// ustawia nastepnego gracza w kolejce jako gracza obstawiajacego
        /// </summary>
        private void NextBet()
        {
            //krzeslo dalej
            int nextChair = NextChair(players[playerBidId].ChairId);

            if ( chairs[nextChair].PlayerId == playerThrowId )
            {
                //koniec licytacji
                EndBeting( );
            }
            else
            {
                players[chairs[nextChair].PlayerId].CurrentAction = DicingActionList.Bet;
                playerBidId = chairs[nextChair].PlayerId;
            }
        }

        /// <summary>
        /// obsluguje zakonczenie licytacji
        /// </summary>
        private void EndBeting()
        {
            //oddajemy kase ktora zostala na stole do gracza rzucajacego
            players[playerThrowId].ActualBetField -= currentMaxBet;
            players[playerThrowId].MoneyField += currentMaxBet;

            //ustawiamy graczowi sygnal aby rzucal
            players[playerThrowId].CurrentAction = DicingActionList.Throwing;

            playerBidId = playerThrowId;

            //ustawiamy stan stolu na rzucanie
            tableAction = DicingActionList.Throwing;
        }

        /// <summary>
        /// wyszukuje id nastepnego zajetego krzesla
        /// </summary>
        /// <param name="chairId">id krzesla po ktorym zaczynamy wyszukiwac</param>
        /// <returns>id nastepnego krzesla</returns>
        private int NextChair( int chairId )
        {
            do
            {
                chairId = ( chairId + 1 ) % chairs.Count;
            } while ( !chairs[chairId].IsPlaying );

            return chairId;
        }

        /// <summary>
        /// wyszukuje id wczesniejszego zajetego krzesla
        /// </summary>
        /// <param name="chairId">id krzesla przed ktorym zaczynamy wyszukiwac</param>
        /// <returns>id wczesniejszego krzesla</returns>
        private int PrevChair( int chairId )
        {
            do
            {
                chairId = ( chairId - 1 + chairs.Count ) % chairs.Count;
            } while ( !chairs[chairId].IsPlaying );

            return chairId;
        }

        /// <summary>
        /// obsluguje sytuacje gdy rzucajacy przegral
        /// </summary>
        public void LostThrow()
        {
            foreach ( DicingPlayer play in players.Values )
            {
                if ( play.PlayerId == playerThrowId )
                    play.Loose( );
                else
                    play.Win( );
            }
            this.tableAction = DicingActionList.NewRound;
            SelectNextThrowing( );
        }

        /// <summary>
        /// obsluguje sytuacje gdy rzucajacy wygral
        /// </summary>
        public void WinThrow()
        {
            foreach ( DicingPlayer play in players.Values )
            {
                if ( play.PlayerId == playerThrowId )
                    play.Win( );
                else
                    play.Loose( );
            }
            this.tableAction = DicingActionList.NewRound;
            SelectNextThrowing( );
        }

        /// <summary>
        /// obsluguje obstawienie przez gracza
        /// </summary>
        /// <param name="money">wysokosc zakladu</param>
        /// <returns>czy sie powiodlo</returns>
        public bool Bet( int money )
        {
            if ( playerBidId == playerThrowId && tableAction == DicingActionList.Bet )
            {
                if ( players[PlayerBidId].MoneyField < money || money < options.MinimumBet)    //sprawdzamy czy mozna tyle postawic
                    return false;
                currentMaxBet = money;
                //gracz obstawia
                players[playerBidId].Bet( money );
            }
            else if ( money > currentMaxBet || players[PlayerBidId].MoneyField < money )    //sprawdzamy czy mozna tyle postawic
                return false;
            else
            {

                //gracz obstawia
                players[playerBidId].Bet( money );

                //zmiejszamy maksymalna mozliwa stawke
                currentMaxBet -= money;
            }

            //nastepny do kolejki proszony o obstawienie ;)
            NextBet( );

            return true;
        }

        /// <summary>
        /// obsluga poczatku gry
        /// </summary>
        /// <returns>czy operacja sie udala</returns>
        public bool Begin()
        {
            //ustawiamy wszystkich graczy gotowych do gry
            foreach ( DicingPlayer play in players.Values )
            {
                play.PrepareForNewRound( );
            }

            //szukamy nowego rzucajacego
            if ( this.tableAction == DicingActionList.FirstRound )
                SelectFirstThrowing( );

            players[playerThrowId].CurrentAction = DicingActionList.Bet;

            //aktualizujemy aktualna maksymalna stawke
            currentMaxBet = 0;

            //zmieniamy stan stolu na licytacje
            tableAction = DicingActionList.Bet;

            return true;
        }

        /// <summary>
        /// ustawia pierwszego rzucajacego w sposob LOSOWY
        /// </summary>
        private void SelectFirstThrowing()
        {
            Random r = new Random( DateTime.Now.Millisecond );
            int who = r.Next( players.Count );
            int i = 0;
            foreach ( DicingPlayer play in players.Values )
            {
                if ( i == who )
                {
                    Throwing( play.PlayerId );  //znalezlismy gracza
                    break;
                }
                else
                    i++;
            }
            
        }

        /// <summary>
        /// ustawia nastepnego rzucajacego z kolejki
        /// </summary>
        private void SelectNextThrowing()
        {
            Throwing( chairs[NextChair(players[playerThrowId].PlayerId)].PlayerId );
        }

        /// <summary>
        /// ustawia danego gracza jako rzucajacego
        /// </summary>
        /// <param name="idPlay">id gracza ktory ma rzucac w tej kolejce</param>
        private void Throwing( int idPlay )
        {
            
            playerThrowId = idPlay;
            playerBidId = idPlay;
        }
    }
}
